File: LanguageService\AbstractLanguageService`2.cs
Web Access
Project: src\src\VisualStudio\Core\Def\Microsoft.VisualStudio.LanguageServices_pxr0p0dn_wpftmp.csproj (Microsoft.VisualStudio.LanguageServices)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.MetadataAsSource;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Structure;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.Text.Shared.Extensions;
using Microsoft.CodeAnalysis.Workspaces.ProjectSystem;
using Microsoft.VisualStudio.Editor;
using Microsoft.VisualStudio.LanguageServices.Implementation.ProjectSystem;
using Microsoft.VisualStudio.LanguageServices.Implementation.Venus;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Text.Outlining;
using Microsoft.VisualStudio.TextManager.Interop;
using Microsoft.VisualStudio.Threading;
using Microsoft.VisualStudio.Utilities;
using Roslyn.Utilities;
 
namespace Microsoft.VisualStudio.LanguageServices.Implementation.LanguageService;
 
internal abstract partial class AbstractLanguageService<TPackage, TLanguageService> : AbstractLanguageService
    where TPackage : AbstractPackage<TPackage, TLanguageService>
    where TLanguageService : AbstractLanguageService<TPackage, TLanguageService>
{
    internal TPackage Package { get; }
 
    private readonly VsLanguageDebugInfo _languageDebugInfo;
 
    // DevDiv 753309:
    // We've redefined some VS interfaces that had incorrect PIAs. When 
    // we interop with native parts of VS, they always QI, so everything
    // works. However, Razor is now managed, but assumes that the C#
    // language service is native. When setting breakpoints, they
    // get the language service from its GUID and cast it to IVsLanguageDebugInfo.
    // This would QI the native lang service. Since we're managed and
    // we've redefined IVsLanguageDebugInfo, the cast
    // fails. To work around this, we put the LS inside a ComAggregate object,
    // which always force a QueryInterface and allow their cast to succeed.
    // 
    // This also fixes 752331, which is a similar problem with the 
    // exception assistant.
    internal object? ComAggregate { get; private set; }
 
    // Note: The lifetime for state in this class is carefully managed.  For every bit of state
    // we set up, there is a corresponding tear down phase which deconstructs the state in the
    // reverse order it was created in.
    internal readonly EditorOptionsService EditorOptionsService;
    internal readonly VisualStudioWorkspaceImpl Workspace;
    internal readonly IVsEditorAdaptersFactoryService EditorAdaptersFactoryService;
 
    /// <summary>
    /// Whether or not we have been set up. This is set once everything is wired up and cleared once tear down has begun.
    /// </summary>
    /// <remarks>
    /// We don't set this until we've completed setup. If something goes sideways during it, we will never register
    /// with the shell and thus have a floating thing around that can't be safely shut down either. We're in a bad
    /// state but trying to proceed will only make things worse.
    /// </remarks>
    private bool _isSetUp;
 
    protected abstract string ContentTypeName { get; }
    protected abstract string LanguageName { get; }
    protected abstract string RoslynLanguageName { get; }
    protected abstract Guid DebuggerLanguageId { get; }
 
    protected AbstractLanguageService(TPackage package)
    {
        Package = package;
 
        Debug.Assert(!this.Package.JoinableTaskFactory.Context.IsOnMainThread, "Language service should be instantiated on background thread");
 
        this.EditorOptionsService = this.Package.ComponentModel.GetService<EditorOptionsService>();
        this.Workspace = this.Package.ComponentModel.GetService<VisualStudioWorkspaceImpl>();
        this.EditorAdaptersFactoryService = this.Package.ComponentModel.GetService<IVsEditorAdaptersFactoryService>();
        this._languageDebugInfo = CreateLanguageDebugInfo();
    }
 
    private IThreadingContext ThreadingContext => this.Package.ComponentModel.GetService<IThreadingContext>();
 
    public override IServiceProvider SystemServiceProvider
        => Package;
 
    /// <summary>
    /// Setup and TearDown go in reverse order.
    /// </summary>
    public async Task SetupAsync(CancellationToken cancellationToken)
    {
        // First, acquire any services we need throughout our lifetime.
        // This method should only contain calls to acquire services off of the component model
        // or service providers.  Anything else which is more complicated should go in Initialize
        // instead.
 
        // Start off a background task to prime some components we'll need for editing.
        Task.Run(() =>
        {
            var formatter = this.Workspace.Services.GetLanguageServices(RoslynLanguageName).GetService<ISyntaxFormattingService>();
            formatter?.GetDefaultFormattingRules();
        }, cancellationToken).Forget();
 
        await this.Package.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
 
        // Creating the com aggregate has to happen on the UI thread.
        this.ComAggregate = Interop.ComAggregate.CreateAggregatedObject(this);
 
        _isSetUp = true;
    }
 
    internal void TearDown()
    {
        if (!_isSetUp)
        {
            throw new InvalidOperationException();
        }
 
        _isSetUp = false;
        GC.SuppressFinalize(this);
    }
 
    ~AbstractLanguageService()
    {
        if (!Environment.HasShutdownStarted && _isSetUp)
        {
            throw new InvalidOperationException("TearDown not called!");
        }
    }
 
    protected virtual void SetupNewTextView(IVsTextView textView)
    {
        Contract.ThrowIfNull(textView);
 
        var wpfTextView = EditorAdaptersFactoryService.GetWpfTextView(textView);
        Contract.ThrowIfNull(wpfTextView, "Could not get IWpfTextView for IVsTextView");
 
        Debug.Assert(!wpfTextView.Properties.ContainsProperty(typeof(AbstractVsTextViewFilter)));
 
        var workspace = Package.ComponentModel.GetService<VisualStudioWorkspace>();
 
        // The lifetime of CommandFilter is married to the view
        wpfTextView.GetOrCreateAutoClosingProperty(v =>
            new StandaloneCommandFilter(
                v, Package.ComponentModel).AttachToVsTextView());
 
        var isMetadataAsSource = false;
        var collapseAllImplementations = false;
 
        var openDocument = wpfTextView.TextBuffer.AsTextContainer().GetRelatedDocuments().FirstOrDefault();
        if (openDocument?.Project.Solution.Workspace is MetadataAsSourceWorkspace masWorkspace)
        {
            isMetadataAsSource = true;
 
            // If the file is metadata as source, and the user has the preference set to collapse them, then
            // always collapse all metadata as source
            var globalOptions = this.Package.ComponentModel.GetService<IGlobalOptionService>();
            var options = BlockStructureOptionsStorage.GetBlockStructureOptions(globalOptions, openDocument.Project.Language, isMetadataAsSource: true);
            collapseAllImplementations = masWorkspace.FileService.ShouldCollapseOnOpen(openDocument.FilePath, options);
        }
 
        ConditionallyCollapseOutliningRegions(textView, wpfTextView, collapseAllImplementations);
 
        // If this is a metadata-to-source view, we want to consider the file read-only and prevent
        // it from being re-opened when VS is opened
        if (isMetadataAsSource && ErrorHandler.Succeeded(textView.GetBuffer(out var vsTextLines)))
        {
            Contract.ThrowIfNull(openDocument);
 
            ErrorHandler.ThrowOnFailure(vsTextLines.GetStateFlags(out var flags));
            flags |= (int)BUFFERSTATEFLAGS.BSF_USER_READONLY;
            ErrorHandler.ThrowOnFailure(vsTextLines.SetStateFlags(flags));
 
            var runningDocumentTable = (IVsRunningDocumentTable)SystemServiceProvider.GetService(typeof(SVsRunningDocumentTable));
            var runningDocumentTable4 = (IVsRunningDocumentTable4)runningDocumentTable;
 
            if (runningDocumentTable4.IsMonikerValid(openDocument.FilePath))
            {
                var cookie = runningDocumentTable4.GetDocumentCookie(openDocument.FilePath);
                runningDocumentTable.ModifyDocumentFlags(cookie, (uint)_VSRDTFLAGS.RDT_DontAddToMRU | (uint)_VSRDTFLAGS.RDT_CantSave | (uint)_VSRDTFLAGS.RDT_DontAutoOpen, fSet: 1);
            }
        }
    }
 
    private void ConditionallyCollapseOutliningRegions(IVsTextView textView, IWpfTextView wpfTextView, bool collapseAllImplementations)
    {
        var outliningManagerService = this.Package.ComponentModel.GetService<IOutliningManagerService>();
        var outliningManager = outliningManagerService.GetOutliningManager(wpfTextView);
        if (outliningManager == null)
            return;
 
        if (textView is IVsTextViewEx viewEx)
        {
            if (collapseAllImplementations)
            {
                // If this file is a metadata-from-source file, we want to force-collapse any implementations
                // to keep the display clean and condensed.
                outliningManager.CollapseAll(wpfTextView.TextBuffer.CurrentSnapshot.GetFullSpan(), c => c.Tag.IsImplementation);
            }
            else
            {
                // Otherwise, attempt to persist any outlining state we have computed. This
                // ensures that any new opened files that have any IsDefaultCollapsed spans
                // will both have them collapsed and remembered in the SUO file.
                // NOTE: Despite its name, this call will LOAD the state from the SUO file,
                //       or collapse to definitions if it can't do that.
                viewEx.PersistOutliningState();
            }
        }
    }
 
    private VsLanguageDebugInfo CreateLanguageDebugInfo()
    {
        var languageServices = this.Workspace.Services.GetLanguageServices(RoslynLanguageName);
 
        return new VsLanguageDebugInfo(
            this.DebuggerLanguageId,
            (TLanguageService)this,
            languageServices,
            this.Package.ComponentModel.GetService<IUIThreadOperationExecutor>());
    }
 
    protected virtual IVsContainedLanguage CreateContainedLanguage(
        IVsTextBufferCoordinator bufferCoordinator, ProjectSystemProject project,
        IVsHierarchy hierarchy, uint itemid)
    {
        return new ContainedLanguage(
            bufferCoordinator,
            this.Package.ComponentModel,
            this.Workspace,
            project.Id,
            project,
            this.LanguageServiceId);
    }
}